# Python中实现web协议支持方式：WSGI
#   wsgi协议定义了wsgi server和wsgi app之间的交互方式
#   浏览器将请求发送给wsgi server，wsgi server将请求封包交给wsgi app
#   wsgi app处理请求将响应头直接相应给浏览器，然后将响应体返回给wsgi server，
#   由wsgi server处理后返给客户端浏览器做渲染。
#
#   wsgiref、flask、django等web框架都是遵循wsgi协议实现 
#
# wsgiref模块是遵循wsgi协议实现http协议的web server简单实现，提供参考价值
# 可以参考wsgiref模块实现web server支持http协议处理，wsgiref模块主要用于学习wsgi协议
# wsgiref.simple_server只能用于测试，不能在生产环境中使用
from wsgiref.util import setup_testing_defaults # setup_testing_defaults函数用于将environ更新为测试环境
from wsgiref.simple_server import make_server # make_server函数用于创建wsgi的http server

def demo01():
    # simple_app为可调用对象callable，作为参数传递给wsgi server, 是最简单的wsgi app实现
    # environ 请求环境变量封装字典对象: wsgi server将请求uri、请求头、请求体等信息封装到environ字典中
    # start_response 函数对象参数： 用户将响应头返回给客户端
    def simple_app(environ, start_response):
        # setup_testing_defaults(environ) # 设置test环境，主要用于测试
        
        for k,v in environ.items(): # 打印请求的环境变量 eniron中包含请求信息和环境变量
            print(k, '=', v)

        status = '200 OK' # 响应码 响应消息
        headers = [('Content-type', 'text/plain; charset=utf-8')] # 响应头
        # 将响应头首先返回给客户端
        start_response(status, headers)

        # 环境变量的可迭代对象, 包装成list可迭代对象
        ret = [("%s: %s\n" % (key, value)).encode("utf-8")
               for key, value in environ.items()]
        return ret # wsgi要求可调用对象需要返回可迭代对象iterable

    print('callable ->', callable(a))
    # 创建http服务，监听8000端口，并传递可调用对象
    # wsgi服务对于可调用对象参数要求：
    #   1. 必须能接收environ和start_response对象(也就是可调用对象必须能传递>=2以上的参数)
    #   2. 可调用对象必须能返回可迭代的对象，返回的可迭代对象被wsgi server用于迭代并返回给浏览器作为body体
    with make_server('127.0.0.1', 8000, simple_app) as httpd:
        print("Serving on port 8000...")
        httpd.serve_forever() # 一直运行不退出

# 可调用对象可以通过内置函数callable(obj)判断是否可调用
# 对于可调用对象并返回可迭代对象当前有3种形式
#   1. 函数并返回可迭代对象(demo01)
#   2. 类并实现__init__(self,environ,start_response)特殊方法
#      接收两个参数, 并实现__iter__特殊方法返回可迭代对象
#   3. 类并实现__call__(self,environ,start_response)特殊方法
#      接受两个参数(较常用)

def demo02():
    class B:
        def __init__(self, environ, start_response):
            for k,v in environ.items(): # 打印请求的环境变量 eniron中包含请求信息和环境变量
                print(k, '=', v)

            status = '200 OK' # 响应码 响应消息
            headers = [('Content-type', 'text/plain; charset=utf-8')] # 响应头
            # 将响应头首先返回给客户端
            start_response(status, headers)

            # 环境变量的可迭代对象
            self.ret = [("%s: %s\n" % (key, value)).encode("utf-8")
                   for key, value in environ.items()]
            
        def __iter__(self):
            yield from self.ret # 迭代时每次返回一个

    print('callable2 ->', callable(B))
    # B 类本身可调用，B(environ, start_response)和调函数一样不过返回本身实例
    # 所以需要实现__iter__使对象可迭代
    with make_server('0.0.0.0', 8000, B) as httpd:
        print('serving on port 8000....')
        httpd.handle_request() # 只处理一次请求，然后退出 

# 该种形式较为常用 
def demo03():
    class A:
        # 构造函数可以不传参数，也可以传参数
        def __init__(self):
            pass
        def __call__(self, environ, start_response):
            for k,v in environ.items(): # 打印请求的环境变量 environ中包含请求信息和环境变量
                print(k, '=', v)

            status = '200 OK' # 响应码 响应消息
            headers = [('Content-type', 'text/plain; charset=utf-8')] # 响应头
            # 将响应头首先返回给客户端
            start_response(status, headers)

            ret = [("%s: %s\n" % (key, value)).encode("utf-8")
                   for key, value in environ.items()]
            return ret 
    a = A()
    print('callable3 ->', callable(a))
    # A 类实现了__call__魔术方法，并返回可迭代对象
    with make_server('127.0.0.1', 8000, a) as httpd:
        print('serving on port 8000....')
        # 发送一次请求
        # import webbrowser
        # webbrowser.open('http://localhost:8000/xyz?abc')

        httpd.handle_request() # 只处理一次请求，然后退出

# 参数获取
def demo04():
    def simple_app(environ, start_response): 
        # 手动获取请求参数等信息       
        # query_string = environ['QUERY_STRING']
        # print(query_string)
        # partition将字符串根据分隔符分为3部分，分隔符左边数据k，分隔符，分隔符右边数据
        # params = { k:v for k,s,v in map(lambda x: x.partition('='), query_string.split('&')) }
        # print(params)
        # print(environ['REQUEST_METHOD'])

        # urllib库获取参数, 该库很常用
        # from urllib.parse import parse_qs
        # params = parse_qs(environ['QUERY_STRING'])
        # print(params)

        # 通过webob三方库获取参数需通过pip install webob
        from webob import Request, Response
        req = Request(environ)
        print(req.method, req.path)
        print(req.GET) # get参数
        print(req.POST) # post表单参数
        print(req.params) # 所有参数
        print(req.headers) # 请求头


        status = '200 OK' # 响应码 响应消息
        headers = [('Content-type', 'text/html; charset=utf-8')] # 响应头
        # 将响应头首先返回给客户端
        start_response(status, headers)

        return ['<h1>ok</h1>'.encode()] # 返回可迭代对象list
    print('callable4 ->', callable(simple_app))
    with make_server('127.0.0.1', 8000, simple_app) as httpd:
        print("Serving on port 8000...")
        httpd.handle_request()

def demo05():
    def app(environ, start_response):
        import webob
        req = webob.Request(environ) # request 对象

        resp = webob.Response() # response 对象
        print(resp.status_code, resp.status) # 默认 200  200 OK
        print(resp.headers) # 响应头默认包含content-type和charset等
        print(resp.headerlist) # header列表
        resp.body = b'ok' # response 的body体，二进制，如果包含汉字使用encode()
        # 返回可迭代对象
        return resp(environ, start_response) # 将会调用response对象的__call__方法
    with make_server('127.0.0.1', 8000, app) as httpd:
        print("Serving on port 8000...")
        httpd.handle_request()

# wsgify 装饰器
def demo06():
    from webob import dec
    import webob
    @dec.wsgify # wsgify装饰器，装饰器wsgify类中的__call__魔术方法实际上实现类似demo05
    def app(req:webob.Request): # 装饰器修饰的方法需要有一个参数接收Request对象
        # return b'ok' # 直接返回二进制内容，wsgify将其封装为body响应体
        # return 'ok' # 直接返回字符串，wsgify会将其转换为二进制
        return webob.Response('ok') # 返回Response对象

    # 可调用的类，使用wsgify装饰器
    class A:
        # 实现__call__魔术方法，并使用wsgify装饰器装饰
        @dec.wsgify
        def __call__(self, req:webob.Request):
            print(req.method, req.path)
            resp = webob.Response('ok')
            resp.content_type = 'text/html'
            resp.set_cookie('a', 'b')
            return resp

    # with make_server('127.0.0.1', 8000, app) as httpd:
    with make_server('127.0.0.1', 8000, A()) as httpd:
        print("Serving on port 8000...")
        httpd.handle_request()

if __name__ == '__main__':
    # demo01()
    # demo02()
    # demo03()
    # demo04()
    # demo05()
    demo06()
